package org.yoki.edu.common.utils.encrypt;


import org.yoki.edu.common.utils.bytes.ByteUtils;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;

/**
 * @author SunnyWen
 * @Description: TODO
 * @date 2017/10/18$ 10:05$
 */
public class EncryptUtils {


    /**
     * SHA1加密
     *
     * @param decript
     * @return
     */
    public static String encryptSHA1(String decript) {
        try {
            MessageDigest digest = MessageDigest
                    .getInstance("SHA-1");
            digest.update(decript.getBytes());
            byte[] messageDigest = digest.digest();
            // Create Hex String
            StringBuffer hexString = new StringBuffer();
            // 字节数组转换为 十六进制 数
            for (int i = 0; i < messageDigest.length; i++) {
                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexString.append(0);
                }
                hexString.append(shaHex);
            }
            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * SHA加密
     *
     * @param decript
     * @return
     */
    public static String encryptSHA(String decript) {
        try {
            MessageDigest digest = MessageDigest
                    .getInstance("SHA");
            digest.update(decript.getBytes());
            byte[] messageDigest = digest.digest();
            // Create Hex String
            StringBuffer hexString = new StringBuffer();
            // 字节数组转换为 十六进制 数
            for (int i = 0; i < messageDigest.length; i++) {
                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexString.append(0);
                }
                hexString.append(shaHex);
            }
            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

    public static byte[] encryptMD5Bytes(String... inputs) {
        byte[] md = null;
        try {
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            mdInst.reset();
            for (String input : inputs) {
                // 使用指定的字节更新摘要
                mdInst.update(input.getBytes());
            }
            // 获得密文
            md = mdInst.digest();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return md;
    }

    /**
     * MD5加密
     */
    public static byte[] encryptMD5Bytes(byte[]... inputs) {
        byte[] md = null;
        try {
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            mdInst.reset();
            for (byte[] bytes : inputs) {
                // 使用指定的字节更新摘要
                mdInst.update(bytes);
            }
            // 获得密文
            md = mdInst.digest();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return md;
    }

    /**
     * MD5加密
     * 获取16进制的字符串
     *
     * @param inputs
     * @return
     */
    public static String encryptMD5Hex(String... inputs) {
        byte[] md5 = encryptMD5Bytes(inputs);
        StringBuffer buf = new StringBuffer();
        for (byte b : md5) {
            buf.append(Integer.toHexString((b >> 4) & 0xf) + Integer.toHexString(b & 0xf));
        }
        return buf.toString();
    }

    /**
     * MD5加密
     * 获取16进制的字符串
     *
     * @param inputs
     * @return
     */
    public static String encryptMD5Hex(byte[]... inputs) {
        byte[] md5 = encryptMD5Bytes(inputs);
        StringBuffer buf = new StringBuffer();
        for (byte b : md5) {
            buf.append(Integer.toHexString((b >> 4) & 0xf) + Integer.toHexString(b & 0xf));
        }
        return buf.toString();
    }

    /**
     * MD5加密
     * 获取16进制的字符串
     *
     * @param inputs
     * @return
     */
    public static String encryptMD5(String... inputs) {
        return encryptMD5Hex(inputs);
    }

    /**
     * MD5加密
     * 获取16进制的字符串
     *
     * @param inputs
     * @return
     */
    public static String encryptMD5(byte[]... inputs) {
        return encryptMD5Hex(inputs);
    }

    /**
     * 获取编码后的字节码数组
     *
     * @param content
     * @param charset
     * @return
     */
    public static byte[] charsetBytes(String content, String charset) {
        if (charset == null || "".equals(charset)) {
            return content.getBytes();
        }
        try {
            return content.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("字符编码出错,您目前指定的编码集是:" + charset);
        }
    }

    /**
     * 加密
     *
     * @param content  需要加密的内容
     * @param password 加密密码
     * @return
     */
    public static byte[] encryptAES(String content, String password) {
        try {
            KeyGenerator kgen = KeyGenerator.getInstance("AES");
            kgen.init(128, new SecureRandom(password.getBytes()));
            SecretKey secretKey = kgen.generateKey();
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
            // 创建密码器
            Cipher cipher = Cipher.getInstance("AES");
            byte[] byteContent = content.getBytes("utf-8");
            // 初始化
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] result = cipher.doFinal(byteContent);
            return result;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解密
     *
     * @param content  待解密内容
     * @param password 解密密钥
     * @return
     */
    public static byte[] decryptAES(byte[] content, String password) {
        try {
            KeyGenerator kgen = KeyGenerator.getInstance("AES");
            kgen.init(128, new SecureRandom(password.getBytes()));
            SecretKey secretKey = kgen.generateKey();
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
            // 创建密码器
            Cipher cipher = Cipher.getInstance("AES");
            // 初始化
            cipher.init(Cipher.DECRYPT_MODE, key);
            // 加密
            byte[] result = cipher.doFinal(content);
            return result;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * This method encodes the plaintext user password according to RFC 2865.
     *
     * @param userPass the password to encrypt
     * @param secret   shared secret
     * @return the byte array containing the encrypted password
     */
    public static byte[] encryptPAP(final String userPass, String secret, byte[] requestAuthenticator) {
        return encryptPAP(ByteUtils.getUtf8Byte(userPass), ByteUtils.getUtf8Byte(secret), requestAuthenticator);
    }

    /**
     * This method encodes the plaintext user password according to RFC 2865.
     *
     * @param userPass the password to encrypt
     * @param secret   shared secret
     * @return the byte array containing the encrypted password
     */
    public static byte[] encryptPAP(final byte[] userPass, byte[] secret, byte[] requestAuthenticator) {
        // 密码必须是16字节的倍数，如果不是则通过补充0达到16的倍数
        // 密码必须小于等于128字节，如果大于128字节，则截取128字节
        byte[] userPassBytes = null;
        if (userPass.length > 128) {
            userPassBytes = new byte[128];
            System.arraycopy(userPass, 0, userPassBytes, 0, 128);
        } else {
            userPassBytes = userPass;
        }

        // declare the byte array to hold the final product
        byte[] encryptedPass = null;
        if (userPassBytes.length < 128) {
            if (userPassBytes.length % 16 == 0) {
                // 如果已经是16的倍数
                encryptedPass = new byte[userPassBytes.length];
            } else {
                // 如果不是是16的倍数
                encryptedPass = new byte[((userPassBytes.length / 16) * 16) + 16];
            }
        } else {
            // 密码必须是16到128字节
            encryptedPass = new byte[128];
        }
        // 将密码复制到encryptedPass中，剩余的由初始化时的0填充
        System.arraycopy(userPassBytes, 0, encryptedPass, 0, userPassBytes.length);
        for (int i = userPassBytes.length; i < encryptedPass.length; i++) {
            encryptedPass[i] = 0;
        }

        // digest shared secret and authenticator
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("md5 digest not available", e);
        }

        byte[] lastBlock = new byte[16];

        for (int i = 0; i < encryptedPass.length; i += 16) {
            md5.reset();
            md5.update(secret);
            md5.update(i == 0 ? requestAuthenticator : lastBlock);
            byte bn[] = md5.digest();

            System.arraycopy(encryptedPass, i, lastBlock, 0, 16);
            // perform the XOR as specified by RFC 2865.
            for (int j = 0; j < 16; j++) {
                encryptedPass[i + j] = (byte) (bn[j] ^ encryptedPass[i + j]);
            }
        }

        return encryptedPass;
    }

    /**
     * Decodes the passed encrypted password and returns the clear-text form.
     *
     * @param encryptedPass encrypted password
     * @param secret        shared secret
     * @return decrypted password
     */
    public static String decryptPAP(byte[] encryptedPass, byte[] secret, byte[] requestAuthenticator) {
        if (encryptedPass == null || encryptedPass.length < 16) {
            // PAP passwords require at least 16 bytes
//            logger.warn("invalid Radius packet: User-Password attribute with malformed PAP password, length = "
//                    + (encryptedPass == null ? 0 : encryptedPass.length) + ", but length must be greater than 15");
            throw new IllegalArgumentException("malformed User-Password attribute");
        }

        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("md5 digest not available", e);
        }

        byte[] lastBlock = new byte[16];

        for (int i = 0; i < encryptedPass.length; i += 16) {
            md5.reset();
            md5.update(secret);
            md5.update(i == 0 ? requestAuthenticator : lastBlock);
            byte bn[] = md5.digest();

            System.arraycopy(encryptedPass, i, lastBlock, 0, 16);

            // perform the XOR as specified by RFC 2865.
            for (int j = 0; j < 16; j++) {
                encryptedPass[i + j] = (byte) (bn[j] ^ encryptedPass[i + j]);
            }
        }

        // remove trailing zeros
        int len = encryptedPass.length;
        while (len > 0 && encryptedPass[len - 1] == 0) {
            len--;
        }
        byte[] passtrunc = new byte[len];
        System.arraycopy(encryptedPass, 0, passtrunc, 0, len);

        // convert to string
        return ByteUtils.getUtf8String(passtrunc);
    }

    /**
     * Encodes a plain-text password using the given CHAP challenge.
     *
     * @param plaintext plain-text password
     * @return CHAP-encoded password
     */
    public static byte[] encryptCHAP(String value, String plaintext) {
        return encryptCHAP(ByteUtils.getUtf8Byte(value), ByteUtils.getUtf8Byte(plaintext));
    }

    /**
     * Encodes a plain-text password using the given CHAP challenge.
     *
     * @param plaintext plain-text password
     * @return CHAP-encoded password
     */
    public static byte[] encryptCHAP(final byte[] value, final byte[] plaintext) {
        // see RFC 2865 section 2.2
        Random random = new Random();
        byte chapIdentifier = (byte) random.nextInt(256);
        byte[] chapPassword = new byte[17];
        chapPassword[0] = chapIdentifier;

        MessageDigest md5 = EncryptUtils.getMd5Digest();
        md5.reset();
        md5.update(chapIdentifier);
        md5.update(plaintext);
        byte[] chapHash = md5.digest(value);

        System.arraycopy(chapHash, 0, chapPassword, 1, 16);
        return chapPassword;
    }

    /**
     * Verifies a CHAP password against the given plaintext password.
     *
     * @return plain-text password
     */
    public static boolean verifyCHAP(final String chapChallenge, final String password, final String plaintext) {
        return verifyCHAP(ByteUtils.getUtf8Byte(chapChallenge), ByteUtils.getUtf8Byte(password), ByteUtils.getUtf8Byte(plaintext));
    }

    /**
     * Verifies a CHAP password against the given plaintext password.
     *
     * @return plain-text password
     */
    public static boolean verifyCHAP(final byte[] chapChallenge, final byte[] password, final byte[] plaintext) {
        if (plaintext == null || plaintext.length == 0) {
            throw new IllegalArgumentException("plaintext must not be empty");
        }
        if (chapChallenge == null || chapChallenge.length != 16) {
            throw new IllegalArgumentException("CHAP challenge must be 16 bytes");
        }
        if (password == null || password.length != 17) {
            throw new IllegalArgumentException("CHAP password must be 17 bytes");
        }

        byte chapIdentifier = password[0];
        MessageDigest md5 = EncryptUtils.getMd5Digest();
        md5.reset();
        md5.update(chapIdentifier);
        md5.update(plaintext);
        byte[] chapHash = md5.digest(chapChallenge);
        // compare
        for (int i = 0; i < 16; i++) {
            if (chapHash[i] != password[i + 1]) {
                return false;
            }
        }
        return true;
    }


    public static MessageDigest getSHADigest() {
        MessageDigest mdInst = null;
        try {
            // 获得MD5摘要算法的 MessageDigest 对象
            mdInst = MessageDigest.getInstance("SHA");
            mdInst.reset();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return mdInst;
    }

    public static MessageDigest getMd5Digest() {
        MessageDigest mdInst = null;
        try {
            // 获得MD5摘要算法的 MessageDigest 对象
            mdInst = MessageDigest.getInstance("MD5");
            mdInst.reset();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return mdInst;
    }

}
